#include "object_sb6mfile.h"

enum { MAX_SUB_OBJECTS = 256 };
typedef struct {
    GLuint data_buffer;
    GLuint vao;
    GLuint index_type;
    GLuint index_offset;
    unsigned int num_sub_objects;
    SB6M_SUB_OBJECT_DECL sub_object[MAX_SUB_OBJECTS];
} model_object;

model_object*
model_loadobject(const char *filename) {
    printf("load object file:%s\n", filename);

    FILE *infile = fopen(filename, "rb");
    if (infile == NULL) {
        printf("fail to open object file\n");
        return NULL;
    }

    size_t filesize = 0;
    fseek(infile, 0, SEEK_END);
    filesize = ftell(infile);
    fseek(infile, 0, SEEK_SET);

    char *data = (char*)malloc(filesize);
    memset(data, 0, sizeof(filesize));
    if (fread(data, filesize, 1, infile) != 1) {
        printf("fail to read file data\n");
        return NULL;
    }

    char *ptr = data;
    SB6M_HEADER *header = (SB6M_HEADER*)ptr;
    ptr += header->size;

    SB6M_VERTEX_ATTRIB_CHUNK *vertex_attrib_chunk = NULL;
    SB6M_CHUNK_VERTEX_DATA *vertex_data_chunk = NULL;
    SB6M_CHUNK_INDEX_DATA *index_data_chunk = NULL;
    SB6M_CHUNK_SUB_OBJECT_LIST *sub_object_chunk = NULL;
    SB6M_DATA_CHUNK *data_chunk = NULL;

    unsigned int i;
    for (i = 0; i < header->num_chunks; i++) {
        SB6M_CHUNK_HEADER * chunk = (SB6M_CHUNK_HEADER *)ptr;
        ptr += chunk->size;
        switch (chunk->chunk_type) {
            case SB6M_CHUNK_TYPE_VERTEX_ATTRIBS:
                vertex_attrib_chunk = (SB6M_VERTEX_ATTRIB_CHUNK *)chunk;
                break;
            case SB6M_CHUNK_TYPE_VERTEX_DATA:
                vertex_data_chunk = (SB6M_CHUNK_VERTEX_DATA *)chunk;
                break;
            case SB6M_CHUNK_TYPE_INDEX_DATA:
                index_data_chunk = (SB6M_CHUNK_INDEX_DATA *)chunk;
                break;
            case SB6M_CHUNK_TYPE_SUB_OBJECT_LIST:
                sub_object_chunk = (SB6M_CHUNK_SUB_OBJECT_LIST *)chunk;
                break;
            case SB6M_CHUNK_TYPE_DATA:
                data_chunk = (SB6M_DATA_CHUNK *)chunk;
                break;
            default:
                printf("invalid chunk type, chunk:%d, type:%d\n", i, chunk->chunk_type); // 书中示例代码仅仅跳出 switch ，因此这里仅仅打印
                break;
        }
    }

    model_object *mo = (model_object*)malloc(sizeof(*mo));
    memset(mo, 0, sizeof(*mo));

    glGenVertexArrays(1, &mo->vao);
    glBindVertexArray(mo->vao);

    if (data_chunk != NULL) {
        glGenBuffers(1, &mo->data_buffer);
        glBindBuffer(GL_ARRAY_BUFFER, mo->data_buffer);
        glBufferData(GL_ARRAY_BUFFER, data_chunk->data_length, (unsigned char*)data_chunk + data_chunk->data_offset, GL_STATIC_DRAW);
    } else {
        unsigned int data_size = 0;
        unsigned int size_used = 0;

        if (vertex_data_chunk != NULL)
            data_size += vertex_data_chunk->data_size;

        if (index_data_chunk != NULL)
            data_size += index_data_chunk->index_count * (index_data_chunk->index_type == GL_UNSIGNED_SHORT ? sizeof(GLushort) : sizeof(GLubyte));

        glGenBuffers(1, &mo->data_buffer);
        glBindBuffer(GL_ARRAY_BUFFER, mo->data_buffer);
        glBufferData(GL_ARRAY_BUFFER, data_size, NULL, GL_STATIC_DRAW);

        if (vertex_data_chunk != NULL) {
            glBufferSubData(GL_ARRAY_BUFFER, 0, vertex_data_chunk->data_size, data + vertex_data_chunk->data_offset);
            size_used += vertex_data_chunk->data_offset;
        }

        if (index_data_chunk != NULL)
            glBufferSubData(GL_ARRAY_BUFFER, size_used, index_data_chunk->index_count * (index_data_chunk->index_type == GL_UNSIGNED_SHORT ? sizeof(GLushort) : sizeof(GLubyte)), data + index_data_chunk->index_data_offset);
    }

    for (i = 0; i < vertex_attrib_chunk->attrib_count; i++) {
        SB6M_VERTEX_ATTRIB_DECL *attrib_decl = &vertex_attrib_chunk->attrib_data[i];
        glVertexAttribPointer(i,
                              attrib_decl->size,
                              attrib_decl->type,
                              attrib_decl->flags & SB6M_VERTEX_ATTRIB_FLAG_NORMALIZED ? GL_TRUE : GL_FALSE,
                              attrib_decl->stride,
                              (GLvoid *)(uintptr_t)attrib_decl->data_offset);
        glEnableVertexAttribArray(i);
    }

    if (index_data_chunk != NULL) {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mo->data_buffer);
        mo->index_type = index_data_chunk->index_type;
        mo->index_offset = index_data_chunk->index_data_offset;
    } else {
        mo->index_type = GL_NONE;
    }

    if (sub_object_chunk != NULL) {
        if (sub_object_chunk->count > MAX_SUB_OBJECTS)
            sub_object_chunk->count = MAX_SUB_OBJECTS;

        for (i = 0; i < sub_object_chunk->count; i++)
            mo->sub_object[i] = sub_object_chunk->sub_object[i];

        mo->num_sub_objects = sub_object_chunk->count;
    } else {
        mo->sub_object[0].first = 0;
        mo->sub_object[0].count = mo->index_type != GL_NONE ? index_data_chunk->index_count : vertex_data_chunk->total_vertices;
        mo->num_sub_objects = 1;
    }

    free(data);
    fclose(infile);

    glBindVertexArray(0);
    printf("successfully load model object\n");
    return mo;
}

void model_freeobject(model_object *mo) {
    if (mo == NULL)
        return;
    glDeleteVertexArrays(1, &mo->vao);
    glDeleteBuffers(1, &mo->data_buffer);
    free(mo);
}

void 
model_render_subobject(model_object *mo, unsigned int object_index, unsigned int instance_count, unsigned int base_instance) {
    glBindVertexArray(mo->vao);
    if (mo->index_type != GL_NONE) {
        union {
            void* ptr;
            unsigned int start;
        } u;
        u.start = mo->sub_object[object_index].first;

        glDrawElementsInstancedBaseInstance(
            GL_TRIANGLES, mo->sub_object[object_index].count, mo->index_type,
            u.ptr, instance_count, base_instance);
    } else {
        glDrawArraysInstancedBaseInstance(
            GL_TRIANGLES, mo->sub_object[object_index].first, mo->sub_object[object_index].count,
            instance_count, base_instance);
    }
    glBindVertexArray(0);
}

void 
model_render(model_object *mo, unsigned int instance_count, unsigned int base_instance) {
    model_render_subobject(mo, 0, instance_count, base_instance);
}

void model_renderdefault(model_object *mo) {
    model_render(mo, 1, 0);
}
